Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | 3x 3x 3x 3x 3x 1x 2x 3x 3x 3x 3x 2x 2x 2x 2x 3x 3x 3x 3x 3x 1x 2x 2x 3x | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { MapPin, Banknote, IndianRupee, Calendar, Clipboard, Briefcase } from 'lucide-react';
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/components/ui/accordion";
import RenderMarkdown from "@/components/custom/RenderMarkdown";
import JobApplicationForm from "./JobApplicationForm";
import JobActions from "./JobActions"; // ✅ Client Component for Buttons
import { Metadata } from "next";
import { strapiImage } from "@/lib/strapi/strapiImage";
import fetchContentType from "@/lib/strapi/fetchContentType";
import { generateMetadataObject } from "@/lib/metadata";
import {formatLPA} from "@/lib/utils";
interface PageProps {
params: Promise<{ documentId: string }>;
}
interface Job {
id: string;
documentId: string;
title: string;
description: string;
location: string;
salary: string;
experience: string;
deadline: string;
createdAt: string;
details: string;
}
export async function generateMetadata({ params }: { params: Promise<{ documentId: string }> }): Promise<Metadata> {
const BASE_URL_NEXT = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
const resolveParams = await params;
const documentId = resolveParams.documentId;
const pageData = await fetchContentType("jobs", {
filters: { documentId },
populate: ["seo.metaImage"],
}, true);
if (!pageData) {
return {
title: "Job Not Found | Bitmutex Technologies",
description: "The requested job does not exist. Browse more job opportunities at Bitmutex Technologies.",
robots: "noindex, nofollow",
};
}
const seo = pageData?.seo;
const metadata = generateMetadataObject(seo);
const seotitle = seo?.metaTitle
? `${seo.metaTitle} | Careers at Bitmutex`
: `${pageData.title || "Untitled Job"} | Careers at Bitmutex`;
let seodescription = seo?.metaDescription || pageData.description || "";
Iif (seodescription.length > 150) {
seodescription = seodescription.substring(0, seodescription.lastIndexOf(" ", 150)) + "...";
}
seodescription += ` - Apply Today! - Deadline on : ${pageData.deadline}`;
metadata.title = seotitle;
metadata.description = seodescription;
metadata.openGraph = {
...(metadata.openGraph as any),
title: seotitle,
description: seodescription,
images: seo?.metaImage
? [{ url: strapiImage(seo.metaImage.url) }]
: { url: `${BASE_URL_NEXT}/bmcs.png` },
url: `${BASE_URL_NEXT}/jobs/${documentId}`,
site_name: "Bitmutex",
locale: "en_US",
type: "article",
};
metadata.alternates = {
canonical: `${BASE_URL_NEXT}/jobs/${documentId}`,
};
return metadata;
}
export default async function JobDetailPage({ params }: PageProps) {
const resolvedParams = await params;
const data = await fetchContentType("jobs", {
filters: { documentId: resolvedParams.documentId },
populate: "*",
});
if (!data || !Array.isArray(data.data) || data.data.length === 0) {
return (
<div className="container mx-auto px-6 py-12 mt-20 text-center text-red-500 text-lg">
🚫 Job not found.
</div>
);
}
const jobData = data.data[0];
const job: Job = {
id: jobData.id,
documentId: jobData.documentId,
title: jobData.title || "Untitled Job",
description: jobData.description || "No description available.",
location: jobData.location || "Not specified",
salary: jobData.salary || "Not disclosed",
experience: jobData.experience || "Not specified",
deadline: jobData.deadline || "No deadline",
createdAt: new Date(jobData.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
}),
details: jobData.details || "**No additional details available.**",
};
return (
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8 mt-16 mb-12">
{/* Breadcrumb or Back Button */}
<div className="mb-6">
<JobActions />
</div>
<div className="grid grid-cols-1 xl:grid-cols-3 gap-10">
{/* Left Column: Job Details */}
<div className="xl:col-span-2 space-y-6">
{/* Main Job Card */}
<Card className="bg-white dark:bg-slate-800 shadow-xl rounded-2xl overflow-hidden border border-gray-100 dark:border-slate-700 transition-all duration-300 hover:shadow-2xl">
<div className="bg-linear-to-r from-blue-50 to-indigo-50 dark:from-slate-900 dark:to-slate-800 p-6 border-b border-gray-100 dark:border-slate-700">
<CardHeader className="p-0">
<CardTitle className="font-heading text-3xl font-bold text-gray-900 dark:text-white leading-tight">
{job.title}
</CardTitle>
</CardHeader>
<p className="font-sans text-gray-600 dark:text-slate-300 mt-2 text-sm md:text-base">
{job.description}
</p>
</div>
<CardContent className="p-6 space-y-5">
{/* Job Info Badges */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="flex items-center space-x-3 text-gray-700 dark:text-slate-300">
<MapPin className="h-5 w-5 text-blue-500 shrink-0" />
<span className="text-sm md:text-base"><strong className="font-heading">Location:</strong> {job.location}</span>
</div>
<div className="flex items-center space-x-3 text-gray-700 dark:text-slate-300">
<Banknote className="h-5 w-5 text-green-500 shrink-0" />
<span className="text-sm md:text-base">
<strong className="font-heading">Salary:</strong>{" "}
<IndianRupee className="h-4 w-4 inline-block align-middle text-emerald-600 drop-shadow-[0_0_6px_#22c55e]" />
{formatLPA(job.salary).replace("₹", "")}
</span>
</div>
<div className="flex items-center space-x-3 text-gray-700 dark:text-slate-300">
<Briefcase className="h-5 w-5 text-purple-500 shrink-0" />
<span className="text-sm md:text-base"><strong className="font-heading">Experience:</strong> {job.experience}</span>
</div>
<div className="flex items-center space-x-3 text-gray-700 dark:text-slate-300">
<Calendar className="h-5 w-5 text-orange-500 shrink-0" />
<span className="text-sm md:text-base"><strong className="font-heading">Posted:</strong> {job.createdAt}</span>
</div>
<div className="flex items-center space-x-3 text-gray-700 dark:text-slate-300">
<Clipboard className="h-5 w-5 text-red-500 shrink-0" />
<span className="text-sm md:text-base"><strong className="font-heading">Deadline:</strong> {job.deadline}</span>
</div>
</div>
{/* Job Details Accordion */}
<Accordion type="single" collapsible defaultValue="job-details" className="mt-6">
<AccordionItem value="job-details" className="border-b border-gray-200 dark:border-slate-700">
<AccordionTrigger className="font-heading text-lg font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
📄 Job Details
</AccordionTrigger>
<AccordionContent className="text-gray-700 dark:text-slate-300 mt-3 leading-relaxed">
<RenderMarkdown content={job.details} />
</AccordionContent>
</AccordionItem>
</Accordion>
</CardContent>
</Card>
</div>
{/* Right Column: Application Form (Sticky) */}
<div className="xl:col-span-1">
<div className="sticky top-24">
<Card className="bg-white dark:bg-slate-800 shadow-lg rounded-2xl border border-gray-100 dark:border-slate-700 overflow-hidden transition-transform duration-300 hover:scale-[1.01]">
<div className="font-heading bg-blue-600 text-white p-4 text-center font-semibold rounded-t-2xl">
🚀 Apply Now
</div>
<CardContent className="p-6">
<JobApplicationForm jobId={job.documentId} jobName={job.title} />
</CardContent>
</Card>
</div>
</div>
</div>
{/* Optional: Add a footer separator or CTA */}
<div className="mt-12 text-center text-gray-500 dark:text-slate-400 text-sm">
Thank you for considering a career at <span className="font-semibold text-blue-600 dark:text-blue-400">Bitmutex</span>.
</div>
</div>
);
} |